module Pan
  
  class Backtrace < Component
    def initialize(e,owner)
      # make sure we get the backtrace out on stdout
      puts '#'*80
      puts e.message
      puts e.backtrace.join("\n")
      puts '#'*80
      @e = ExceptionExplorer.new(e)
      @owner = owner
      @doc = Document.new(nil, "Exception")
      @doc.add_css("/css/ruby.css")
      @doc.add_css("/css/changes.css")
    end
    
    def render_on(html)
      @doc.render_on(html) { |html|
        # the parsing of the backtrace lines comes from merb
        html.div("class"=>"backtrace") {
          html.heading(html.escape(@e.message),3)
          # html.link("[close]") { @owner.pop }
          html.anchor("href"=>session.req.application_url) { html.text("[new session]") }
          html.link("[more levels]") { @owner.show_levels += 1 }
          html.link("[more lines]") { @owner.show_lines += 2 }
          html.table("border"=>"1","cellspacing"=>"0") {
            html.tr {
              html.th { html.text("File") }
              html.th { html.text("#") }
              html.th { html.text("Method") }
              html.th { html.text("Source") }
            }
            @e.backtrace(@owner.show_levels, @owner.show_lines) do |file, lineno, meth, lines|
              html.tr {
                html.td { html.text(file) }
                html.td { html.text(lineno) }
                html.td { html.text(meth) }
                render_source(lines, html)
              }
            end
          }
        }
      }
    end
    
    def render_source(lines, html)
      convertor = Syntax::Convertors::HTML.for_syntax("ruby")
      hlines = lines.map{|lineno,line,bool| [lineno, convertor.convert(line), bool]}
      html.td { 
        html.div("class"=>"ruby") { 
          hlines.each do |lineno,line,bool|
            if bool
              html.div("class"=>"active-line") {
                html.text("#{line}")
              }
            else
              html.text("#{line}")
            end
          end
        }
      }
    end
  end
end

class ExceptionExplorer
  def initialize(e)
    @e = e
  end

  def message
    @e.message
  end

  def parse_line(line)
    line.scan(/(.*?):(\d+)(?::in `(.*?)')?/).first
  end

  def backtrace(show_levels, show_lines)
    @e.backtrace[0..show_levels-1].each do |line|
      file, lineno, meth = parse_line(line)
      src_lines = read_source_lines(file, lineno, show_lines)
      yield(file, lineno, meth, src_lines)
    end
  end

  def read_source_lines(file, lineno, show_lines)
    code_for_file(file, lineno, show_lines) rescue []
  end

  def code_for_file(file, line, lines_around = 4)
    lines = File.readlines(file)
    current = line.to_i - 1

    first = current - lines_around
    first = first < 0 ? 0 : first

    last = current + lines_around
    last = last > lines.size ? lines.size : last

    log = lines[first..last]

    area = []

    log.each_with_index do |line, index|
      index = index + first + 1
      area << [index, line.chomp, index == current + 1]
    end
    area
  end

end

